const {reduce} = require('./arrayUtils')

const forEach = (fn, array) => {
  for(let i = 0; i < array.length; i++) {
    fn(array[i])
  }
}

const forEachObject = (fn, obj) => {
  for(let prop in obj) {
    if(obj.hasOwnProperty(prop)) {
      fn(prop, obj[prop])
    }
  }
}

const unless = (fn, predicate) => {
  if(!predicate) {
    fn()
  }
}

const times = (fn, times) => {
  for(let i = 0; i < times; i++) {
    fn(i)
  }
}

const every = (fn, array) => {
  let result = true
  for(const val of array) {
    result = result && fn(val)
  }
  return result
}

const some = (fn, array) => {
  let result = false
  for(const val of array) {
    result = result || fn(val)
  }
  return result
}

const sortBy = prop => (a, b) => {
  if(a[prop] < b[prop]) {
    return -1
  }
  if(a[prop] > b[prop]) {
    return 1
  }
  return 0
}

const tap = fn => val => (fn(val), val)

//接受一个给定的多参数函数，把它转化为只接受一个参数的函数。
const unary = fn => {
  if(fn.length === 1) {
    return fn
  } else {
    return arg => fn(arg)
  }
}

//将允许运行一次指定的函数。
const once = fn => {
  let done = false
  return function() {
    if(done) {
      return undefined
    }
    done = true
    return fn.apply(this, arguments)
  }
}

const memoized = fn => {
  const lookupTable = {}
  return arg => lookupTable[arg] || (lookupTable[arg] = fn(arg))
}

const curry = fn => {
  if(typeof fn !== 'function') {
    throw new Error('No function provided.')
  }
  return function curriedFn(...args) {
    if(args.length < fn.length) {
      return function() {
        return curriedFn.apply(null, args.concat([].slice.call(arguments)))
      }
    }
    return fn.apply(null, args)
  }
}

//适合处理不是为curry而设计的函数，如setTimeout, JSON.stringify。
const partial = (fn, ...partialArgs) => (...callArgs) => {
  let args = partialArgs.slice()
  for(let i = 0, j = 0; i < partialArgs.length && j < callArgs.length; i++) {
    if(args[i] === undefined) {
      args[i] = callArgs[j++]
    }
  }
  return fn.apply(null, args)
}

const compose = (...fns) => value => {
  return reduce((acc, fn) => fn(acc), value, fns.reverse())
}

const pipe = (...fns) => value => {
  return reduce((acc, fn) => fn(acc), value, fns)
}

module.exports = {
  forEach, forEachObject, unless, times, every, some, sortBy,
  tap, unary, once, memoized, curry, partial, compose, pipe
}
